﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xant.Querier.Core;
using Xant.Querier.Interface;
using LinqExpressions = System.Linq.Expressions;

namespace Xant.Querier.Compilers
{
    public static class QueryableExecutor
    {

        public static IQueryable<T> RunQuery<T>(IQueryable<T> querySource, IQuery query)
        {
            if (typeof(T) != query.SourceEntityType)
                throw new InvalidOperationException(string.Format("提供的源数据类型({0})与查询对象的源数据类型({1})不符。", typeof(T).FullName, query.SourceEntityType.FullName));
            var result = querySource;
            var lambdaCompiler = new LambdaExpressionCompiler<T>(query);
            if (query.RootExpression != null)
            {
                var expression = lambdaCompiler.Compile();
                result = result.Where(expression);
            }
            if (query.OrderClause != null)
            {
                result = result.OrderBy(query.OrderClause);
            }
            if(query.Pagination!=null)
            {
                result = result.Skip(query.Pagination.PageSize * query.Pagination.PageIndex).Take(query.Pagination.PageSize);
            }
            return result;
        }
        
        public static LinqExpressions.Expression OrderBy<T>(Type type, IOrderClause orderClause)
        {
            if (orderClause == null || !orderClause.HasElements)
                throw new ArgumentNullException("orderClause");
            var elementType = typeof(T);
            var paramExpr = LinqExpressions.Expression.Parameter(elementType, "p");
            var list = new List<T>();
            LinqExpressions.Expression sourceExpression = list.AsQueryable().Expression;// LinqExpressions.ConstantExpression.New(typeof(EnumerableQuery<T>));
            for (int i = 0; i < orderClause.Elements.Count; i++)
            {
                var orderElement = orderClause.Elements[i];
                var propertyName = (orderElement.Factor as IField).Name;
                var orderByMember = LinqExpressions.Expression.PropertyOrField(paramExpr, propertyName);
                var orderByExp = LinqExpressions.Expression.Lambda(orderByMember, paramExpr);
                Type propertyType = elementType.GetProperty(propertyName).PropertyType;
                string methodName;
                if (i == 0)
                    methodName = orderElement.Rule == OrderRule.Ascending ? "OrderBy" : "OrderByDescending";
                else
                    methodName = orderElement.Rule == OrderRule.Ascending ? "ThenBy" : "ThenByDescending";
                sourceExpression = LinqExpressions.Expression.Call(typeof(Queryable), methodName,
                    new Type[] { elementType, propertyType },
                    sourceExpression, orderByExp);
            }
            return sourceExpression;
        }

        public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, IOrderClause orderClause)
        {
            if (orderClause == null || !orderClause.HasElements)
                throw new ArgumentNullException("orderClause");
            LinqExpressions.Expression resultExpression = source.Expression;
            var elementType = typeof(T);
            var paramExpr = LinqExpressions.Expression.Parameter(elementType, "p");
            for (int i = 0; i < orderClause.Elements.Count; i++)
            {
                var orderElement = orderClause.Elements[i];
                var propertyName = (orderElement.Factor as IField).Name;
                var orderByMember = LinqExpressions.Expression.PropertyOrField(paramExpr, propertyName);
                var orderByExp = LinqExpressions.Expression.Lambda(orderByMember, paramExpr);
                Type propertyType = elementType.GetProperty(propertyName).PropertyType;
                string methodName;
                if (i == 0)
                    methodName = orderElement.Rule == OrderRule.Ascending ? "OrderBy" : "OrderByDescending";
                else
                    methodName = orderElement.Rule == OrderRule.Ascending ? "ThenBy" : "ThenByDescending";
                resultExpression = LinqExpressions.Expression.Call(typeof(Queryable), methodName,
                    new Type[] { elementType, propertyType },
                    resultExpression, orderByExp);
            }
            var result = source.Provider.CreateQuery<T>(resultExpression);
            //var rlt2 = new List<T>().AsQueryable().Provider.CreateQuery<T>(resultExpression);//即使是新建一个List对象，然后调用CreateQuery获得的结果也和上面一行一样
            return (IOrderedQueryable<T>)result;
        }

        public static IOrderedQueryable<T> OrderBy2<T>(this IQueryable<T> source, IOrderClause orderClause)
        {
            if (orderClause == null || !orderClause.HasElements)
                throw new ArgumentNullException("orderClause");
            var result = source;
            var elementType = typeof(T);
            var paramExpr = LinqExpressions.Expression.Parameter(elementType, "p");
            for (int i = 0; i < orderClause.Elements.Count; i++)
            {
                var orderElement = orderClause.Elements[i];
                var propertyName = (orderElement.Factor as IField).Name;
                var orderByMember = LinqExpressions.Expression.PropertyOrField(paramExpr, propertyName);
                var orderByExp = LinqExpressions.Expression.Lambda(orderByMember, paramExpr);
                Type propertyType = elementType.GetProperty(propertyName).PropertyType;
                string methodName;
                if (i == 0)
                    methodName = orderElement.Rule == OrderRule.Ascending ? "OrderBy" : "OrderByDescending";
                else
                    methodName = orderElement.Rule == OrderRule.Ascending ? "ThenBy" : "ThenByDescending";
                LinqExpressions.Expression lambda = LinqExpressions.Expression.Call(typeof(Queryable), methodName,
                    new Type[] { elementType, propertyType },
                    result.Expression, orderByExp);
                result = result.Provider.CreateQuery<T>(lambda);
            }
            return (IOrderedQueryable<T>)result;
        }

        public static IOrderedQueryable<T> ThenOrderBy<T>(this IOrderedQueryable<T> source, IOrderClause orderClause)
        {
            var paramExpr = LinqExpressions.Expression.Parameter(typeof(T), "p");
            return source.ThenOrderBy(orderClause, paramExpr);
        }

        private static IOrderedQueryable<T> ThenOrderBy<T>(this IOrderedQueryable<T> source, IOrderClause orderClause, LinqExpressions.ParameterExpression paramExpr)
        {
            if (orderClause == null || !orderClause.HasElements)
                return source;
            var result = source;
            for (int i = 0; i < orderClause.Elements.Count; i++)
            {
                result = result.ThenOrderBy(orderClause.Elements[i], paramExpr);
            }
            return result;
        }

        private static IOrderedQueryable<T> ThenOrderBy<T>(this IOrderedQueryable<T> source, IOrderElement orderElement, LinqExpressions.ParameterExpression paramExpr)
        {
            if (orderElement== null)
                throw new ArgumentNullException("orderElement");
            var type = typeof(T);
                var propertyName = (orderElement.Factor as IField).Name;
                var orderByMember = LinqExpressions.Expression.PropertyOrField(paramExpr, propertyName);
                var orderByExp = LinqExpressions.Expression.Lambda(orderByMember, paramExpr);
                //LinqExpressions.Expression sourceExpression = list.AsQueryable().Expression;
                Type sourcePropertyType = type.GetProperty(propertyName).PropertyType;
                var methodName = orderElement.Rule == OrderRule.Ascending ? "ThenBy" : "ThenByDescending";
                LinqExpressions.Expression lambda = LinqExpressions.Expression.Call(typeof(Queryable), methodName,
                    new Type[] { type, sourcePropertyType },
                    source.Expression, orderByExp);
            var    result = (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(lambda);
            return result;
        }
    }
}
